/*
 * jQuery UI Form Validation @VERSION
 *
 * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/formvalidation
 *
 * Depends:
 *	ui.core.js
 *	ui.position.js
 *	ui.tooltip.js
 *	ui.progressbar.js
 */
(function($) {

var validators = {};
var messages = {};

$.formvalidation = function(s, rules) {
	var errors = {};
	var valid = true;

	if (!rules) {
		errors.valid = true;
		return errors;
	}

	$.each(rules, function(rule) {
		if ($.isFunction(validators[rule])){

			var result = validators[rule].call($.formvalidation, s, rules[rule]);

			if ($.isArray(result)) {
				valid = result[0];
				errors[rule] = [result[1], result[2]];
				return;
			}

			if (result !== true) {
				valid = false;
				errors[rule] = messages[rule];
				return;
			}

			errors[rule] = null;
		}
	});

	errors.valid = valid;

	return errors;
};

$.extend($.formvalidation, {

	validators: function() {
		return validators;
	},

	addValidator: function(name, method, message) {
		validators[name] = method;
		return $.formvalidation.addMessage(name, message);
	},

	addValidators: function(validators) {
		$(validators).each(function() {
			$.formvalidation.addValidator(this[0], this[1], this[2]);
		});

		return $.formvalidation;
	},

	messages: function() {
		return messages;
	},

	addMessage: function(name, message) {
		messages[name] = message;
		return $.formvalidation;
	}

});

$.extend($.expr[':'], {
	valid: function(element) {
		if (!$.data( element, 'formvalidation' ))
			return false;

		$(element).formvalidation('validate');
		return $(element).formvalidation('isvalid');
	}
});




/*
this probably needs to be in a seperate file for each language as dates, currency, phone and postal formats differ
*/

$.formvalidation.addValidators([

	['required', function(value, param) {
		return (param && value !== '');
	}, ''],

	['number', function(value) {
		var isValid = (/(^-*\d+$)|(^-*\d+\[,|.]?\d+$)/i).test(value);

		return (value === '' || isValid);
	}, 'must be a number'],

	['date', function(value) {
		var isValidHtml5 =  (/^\d{4}[\-\/\s]?((((0[13578])|(1[02]))[\-\/\s]?(([0-2][0-9])|(3[01])))|(((0[469])|(11))[\-\/\s]?(([0-2][0-9])|(30)))|(02[\-\/\s]?[0-2][0-9]))$/).test(value);

		var isValid = (/^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[1,3-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{4})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.|\\)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{4})$/).text(value);

		return (value === '' || isValidHtml5 || isValid);
	}, 'must be a valid date in the format yyyy-dd-mm or dd/mm/yyyy'],

	// following by Scott Gonzalez: http://projects.scottsplayground.com/iri/
	['url', function(value) {
		var isValid = (/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i).test(value);

		return (value === '' || isValid);
	}, 'must be a valid url'],

	['email', function(value) {
		var isValid = (/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i).test(value);

		return (value === '' || isValid);
	}, 'must be a valid email'],

	// password strength - should this be password and not have a minimum value
	['strength', function (value, param) {
		var strength = 0;

		var uppercaseCount = (value.match(new RegExp('([A-Z])', 'g')) || []).length;
		var lowercaseCount = (value.match(new RegExp('([a-z])', 'g')) || []).length;
		var digitCount = (value.match(new RegExp('([0-9])', 'g')) || []).length;
		var characterCount = (value.match(new RegExp('([^a-z0-9\s])', 'gi')) || []).length;

		// Password length
		var len = value.length;
		if (len > 7) {
			strength += 25; // -- 8 or more
		} else if (len > 4 && len < 8) {
			strength += 10; // -- 5 to 7 characters
		} else if (len < 5 && len > 0) {
			strength += 5; // -- Less than 4
		}

		// Letters
		if (lowercaseCount > 0) {
			strength += (uppercaseCount > 0 ?  20 : 10); // lowercase or mixed case
		}

		// Number Count
		if(digitCount == 1){ strength += 10; }else if(digitCount > 1){ strength += 20; }

		// Character Count
		if(characterCount == 1){ strength += 10; }else if(characterCount > 1){ strength += 25; }


		// Bonus
		if (digitCount > 0 && uppercaseCount + lowercaseCount > 0) {
			strength += (characterCount > 0 ? 5 : 2); // -- bonus for characters
		}
		if (digitCount > 0 && uppercaseCount > 0 && lowercaseCount > 0 && characterCount > 0) {
			strength += 5; // -- Mixed case letters, numbers, and characters
		}

		var message = 'useless';
		if (strength >= 90) {
	 		message = "very strong";
	 	} else if (strength >= 80) {
	 		message = "strong";
		} else if (strength >= 70) {
	 		message = "medium";
		} else if (strength >= 60) {
	 		message = "fair";
		} else if (strength >= 50) {
	 		message = "weak";
		} else if (strength >= 25) {
	 		message = "very weak";
		}

		return [(strength >= param), '<strong>'+ message +'</strong> password strength', strength];
	}, null],

	// UK Specific
	['postcode', function(value) {
		var isValid = (/^([a-z]{1,2})([0-9]{1,2})([\s]{0,1})([0-9]{1})([A-Za-z]{2})$/i).test(value);

		return (value === '' || isValid);
	}, 'must be a valid uk postcode'],

	['telfax', function(value) {
		var isValid = (/^([0-9]{11})$/i).test(value);

		return (value === '' || isValid);
	}, 'must be a valid uk telephone/fax number'],

	// RegEx Pattern Matcher
	['pattern', function(value, param) {
		var isValid = new RegExp(param, 'i').test(value);

		return (value === '' || isValid);
	}, 'does not match the required pattern'],

	['min', function(value, param) {
		var isValid = (parseInt(value, 10) >= parseInt(param, 10));

		return (value === '' || isValid);
	}, 'must be no less than {0}'],

	['max', function(value, param) {
		var isValid = (parseInt(value, 10) <= parseInt(param, 10));

		return (value === '' || isValid);
	}, 'must be no more than {0}'],

	['maxlength', function(value, param) {
		var isValid = (value.length <= param);

		return (value === '' || isValid);
	}, 'must be no longer than {0} character{s}'], // {s} is a special replace for plurals

	['lessthan', function(value, param) {
		var identifier = (param.indexOf('#') < 0 ? '#': '') + param;
		var isValid = parseInt(value, 10) < parseInt($(identifier).val(), 10);

		return (value === '' || isValid);
	}, 'must be less than the value of {0}'],

	['morethan', function(value, param) {
		var identifier = (param.indexOf('#') < 0 ? '#': '') + param;
		var isValid = parseInt(value, 10) > parseInt($(identifier).val(), 10);

		return (value === '' || isValid);
	}, 'must be more than the value of {0}'],

	['equalto', function(value, param) {
		var identifier = (param.indexOf('#') < 0 ? '#': '') + param;
		var isValid = $(identifier).val();

		return (value === '' || isValid);
	}, 'must be equal to the value of {0}']
]);








$.widget("ui.formvalidation", {
	options: {
		rules: {
			'required': false,
			'type': 'text',
			'max': null,
			'maxlength': null,
			'min': null,
			'pattern': null,
			'step': null,
			'lessthan': null,
			'morethan': null,
			'equalto': null
		}
	},

	_create: function() {

		var self = this;

		if (this._canvalidate()) {
			this.originalTitle = this.element.attr('title');

			this.hasFocus = false;

			this.element
				.bind('focus.formvalidation', function() {
					self.hasFocus = true;
					self.validate();
				})
				.bind('blur.formvalidation', function() {
					self.hasFocus = false;
					self.validate();
				})
				.bind('change.formvalidation keyup.formvalidation mousemove.formvalidation', function() {
					self.validate();
				});
			this.message = $('<div class="ui-formvalidation-message" />');

			if ($.ui.progressbar)
				this.strength = $('<div class="ui-formvalidation-strength" />').progressbar({value: 0}).hide();

			if ($.ui.tooltip) {
				this.element.tooltip({
					content: function() {
						if (self.option('validationMessage') === '')
							return '';

						return $('<div></div>')
							.append(self.strength)
							.append(self.message);
					}
				});
			}

		} else {

			this.element
				.find(':input').formvalidation();

			this.helper = $('<div />')
				.addClass('ui-widget'
						+ ' ui-formvalidation-message'
						+ ' ui-state-error'
						+ ' ui-corner-all')
				.appendTo(this.element)
				.hide();

			var submitAction = function() {
				if (!self.validate().valid) {
					var invalidfields = self.element.find(':[aria-invalid=true]').length;

					self.helper
						.html('<p><span class="ui-icon ui-icon-alert" style="float: left; margin-right: .3em;" /><strong>There '+ (invalidfields > 1 ? 'were' : 'was') + ' '+ invalidfields +' error'+ (invalidfields > 1 ? 's' : '') + '</strong> They have been highlighted.</p>')
						.filter(':hidden')
						.fadeIn('slow');

					return false;
				} else {
					self.helper.filter(':visible').fadeOut('slow');
				}
			};

			if ((/form/i).test(this.element[0].nodeName)) {
				//this.element.bind('submit.formvalidation', submitAction);
				this.element.find(':submit').bind('click.formvalidation', submitAction);
			} else {
				this.element.find(':submit').bind('click.formvalidation', submitAction);
			}
		}

		this.validate();
	},

	disable: function() {
		this._setOption('disabled', true);
		this.element.tooltip('disable').removeClass('ui-tooltip-disabled ui-state-disabled').attr("aria-disabled", false);
	},

	enable: function() {
		this._setOption('disabled', false);
		this.element.tooltip('enable');
	},

	destroy: function() {
		this.element
			.find(':submit')
			.unbind('.formvalidation');

		this.element
			.removeClass("ui-state-error")
			.removeAttr("aria-invalid")
			.removeAttr("aria-required")
			.attr('title', this.originalTitle);

		if ($.ui.tooltip)
			this.element.tooltip('destroy');

		if (this.strength)
			this.strength.progressbar('destroy').remove();

		if (this.message)
			this.message.remove();

		$.Widget.prototype.destroy.apply(this, arguments);
	},

	_labels: function() {
		var labels = [];

		var l = this.element.parents('label')
			.add('label[for='+ this.element.attr('id') +']')
			.not('.ui-formvalidation-help');

		l.each(function() {
				$(this).contents().each(function() {
					if (this.nodeType === 3) {
						labels.push(this.nodeValue);
					}
				});
			});

		return labels;

	},

	errors: function() {
		if (this.options.disabled)
			return;

		return this.option('errors');
	},

	validate: function() {
		if (this.options.disabled)
			return;

		var self = this,
				validation = {valid: true},
				element = this.element;

		if (this._canvalidate()) {

			var rules = {};

			self.element.removeAttr("aria-required");

			$.each(this.options.rules, function(key, val) {
				var attrVal = val;

				if (key === 'type') {
					if (attrVal === 'text') {
						key = element[0].getAttribute(key); // "date" is not recognised if we use jQuery.attr()
					} else {
						key = val
					}
					attrVal = true;
				} else if (!attrVal) {
					attrVal = element.attr(key);
				}

				if (!$.isFunction(validators[key]))
					return;

				if (typeof(attrVal) != 'undefined' && attrVal != null && attrVal != -1 && attrVal != false && attrVal != '') {
					rules[key] = (key == attrVal) ? true : attrVal;
					if (key === 'required') self.element.attr("aria-required", true);
				}
			});

			var val = (element[0].value ? element.val() : element.text());
			$.extend(validation, $.formvalidation(val, rules));

			(validation.valid) ?element.removeAttr('aria-invalid') : element.attr('aria-invalid', true);
			this._setOption('errors', (validation.valid ? {} : validation));
			this._setOption('invalid', !validation.valid);
			this._setOption('validationMessage', '');

			if (validation.strength || !validation.valid) {
				$.each(validation, function(error, message){
					if (!validation.strength && (error === 'valid' || self.option('validationMessage') !== '')) {
						return;
					}

					var validationMessage = '';

					if ($.isArray(message))
						message = message.join(':');

					if (message && message !== '') {
						message = message
							.replace('{0}', rules[error])
							.replace('{s}', (parseInt(rules[error]) == 1 ? '' : 's' ));

						var label = self._labels()[0] || element.attr('name');

						validationMessage = label +' '+ message;
					}

					self._setOption('validationMessage', validationMessage);
				});
			}

			// this.message.empty();

			if (!self.isvalid()) {
				element.addClass('ui-state-error');

				if (self.hasFocus &&
						((rules.strength && rules.strength > 0) || self.option('validationMessage') !== '')) {
					var messageparts = this.option('validationMessage').split(':');

					if (messageparts.length > 1) {
						this.message.html(messageparts[0]);
						this.strength.show().progressbar('value', parseInt(messageparts[1]));
					} else {
						this.message.html(self.option('validationMessage'));
						this.strength.hide();
					}

					($.ui.tooltip && element.tooltip('widget').not(':visible') && element.trigger('focus.tooltip'));
				} else {
					($.ui.tooltip && element.tooltip('widget').is(':visible') && element.trigger('blur.tooltip'));
				}
			} else {
				element.removeClass('ui-state-error');
				($.ui.tooltip && element.tooltip('widget').is(':visible') && element.trigger('blur.tooltip'));
			}
		} else {
			if (element.find(':ui-formvalidation:not(:valid)').length > 0) {
				element.attr('aria-invalid', true);
				validation.valid = false;
			} else {
				element.removeAttr('aria-invalid');
			}
		}

		this._trigger('validate.formvalidation', null, validation);

		return validation;
	},

	isvalid: function() {
		if (this.options.disabled)
			return;

		return this.element.attr('aria-invalid') !== 'true';
	},

	_canvalidate: function() {
		return ((/input|select|textarea/i).test(this.element[0].nodeName) || this.element.children().length === 0);
	}
});

})(jQuery);
